/*  FUSE: Filesystem in Userspace
  Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>

  This program can be distributed under the terms of the GNU GPL.
  See the file COPYING.

  gcc -Wall `pkg-config fuse --cflags --libs` fusexmp.c -o fusexmp
*/

#include "optimfs.h"
//extern "C++"
//{
static int optim_getattr(const char *path, struct stat *stbuf)
{
	int res = 0;
	//GetLevels
	char *filePath;
	res = getFilePath(path,&filePath);
	if(res == 0)
		res = lstat(filePath,stbuf);		
	else
	{
		memset(stbuf,0,sizeof(struct stat));
		stbuf->st_mode = 16877;
		stbuf->st_atime = stbuf->st_mtime = stbuf->st_ctime = time(NULL);
		res = 0;
	}
	
	return -res;
}

static int optim_access(const char *path, int mask)
{
int res;

//	res = access(path, mask);
//	if (res == -1)
//		return -errno;

	return 0;
}

static int optim_readlink(const char *path, char *buf, size_t size)
{
	return 0;
}


static int optim_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
		       off_t offset, struct fuse_file_info *fi)
{

	(void) offset;
	(void) fi;

	FindMusicFolders(path,buf,filler); 

	return 0;
}

static int optim_mknod(const char *path, mode_t mode, dev_t rdev)
{

	return 0;
}

static int optim_mkdir(const char *path, mode_t mode)
{
	//needed?
	return 0;
}

static int optim_unlink(const char *path)
{
	//remove from file system, remove from db
	//GetLevels
	int res;
	char *filePath;
	res = getFilePath(path,&filePath);
	if(res == 0)
	{
		char query[1024];
	//	unlink(filePath);
        char ** level_key;
        char *folderPath;
        int numLevels = getLevelKeys(&level_key,path);

        //Find the FolderInfo for the corresponding folder
        if(numLevels == 0)
        {
                folderPath = path;
        }
        else
        {
                folderPath = level_key[0];
        }

		 int indexFolderInfo = FindFolderInfo(folderPath);
        	sprintf(query,"DELETE from %s where path=\"%s\"",folderInfo.folderData[indexFolderInfo]->tableName,filePath);
	       int  i = sqliteExecute(db,query,0,0);
		if(i == 0)
		{
			unlink(filePath);
		}
			//TODO:unlink from db
		freeLevelKeys(level_key);
	}
	return -res;
}

static int optim_rmdir(const char *path)
{
	//remove the folder? or files which fall into that folder
	//TODO
	return 0;
}

static int optim_symlink(const char *from, const char *to)
{

	return 0;
}

static int optim_rename(const char *from, const char *to)
{
	//Rename real file, update in db. 
	//remove from file system, remove from db
	//GetLevels
	int res;
	char *filePath;
	res = getFilePath(from,&filePath);
	if(res == 0)
	{
		rename(filePath,to);
		//TODO:update db
	}
	return -res;
}

static int optim_link(const char *from, const char *to)
{

	return 0;
}

static int optim_chmod(const char *path, mode_t mode)
{
	//prepoperties?? change?
	return 0;
}

static int optim_chown(const char *path, uid_t uid, gid_t gid)
{

	return 0;
}

static int optim_truncate(const char *path, off_t size)
{

	return 0;
}

static int optim_utimens(const char *path, const struct timespec ts[2])
{
	return 0;
}

static int optim_open(const char *path, struct fuse_file_info *fi)
{

	int res;
	char *filePath;
	int fd;
	res = getFilePath(path,&filePath);
	if(res == 0)
	{
		fd = open(filePath,fi->flags);
		if(fd == 0)
		{
			res = errno;
		}
		fi->fh = fd;
	}	
		
	return res;
}

static int optim_read(const char *path, char *buf, size_t size, off_t offset,
		    struct fuse_file_info *fi)
{
	//Get from list
	//open_files *fileInfo = GetFilePathFromFD(fi->fh);
	//fileInfo->path
	int res = pread(fi->fh, buf, size, offset);
	if (res == -1)
		res = -errno;

	return res;	
}

static int optim_write(const char *path, const char *buf, size_t size,
		     off_t offset, struct fuse_file_info *fi)
{
	int res = 0;
	res = pwrite(fi->fh, buf, size, offset);
	if (res == -1)
		res = -errno;
	return res;
}

static int optim_statfs(const char *path, struct statvfs *stbuf)
{

	return 0;
}

static int optim_release(const char *path, struct fuse_file_info *fi)
{
	//write
	//push all metadata of the file to the db
	
	int res;
	char *filePath;
	char **level_key;
	res = getFilePath(path,&filePath);  
	if(res != 0)
		return -res;
		
	int numLevels = getLevelKeys(&level_key,path);
	
	
	//Find the FolderInfo for the corresponding folder
        char *folderPath;
        //Find the FolderInfo for the corresponding folder
        if(numLevels == 0)
        {
                folderPath = path;
        }
        else
        {
                folderPath = level_key[0];
        }
        int indexFolderInfo = FindFolderInfo(folderPath);
	

	if(indexFolderInfo == -1)
	{
		return -1;
	}
	
//	if((numLevels-1) <= folderInfo.folderData[indexFolderInfo]->rules->numelements)
//	{
		//s
//	}	
	readFileData(filePath,1);
	close(fi->fh);
	freeLevelKeys(level_key);
	return 0;
}

static int optim_fsync(const char *path, int isdatasync,
		     struct fuse_file_info *fi)
{

	return 0;
}

static struct optim_oper : fuse_operations {
	
	optim_oper()
	{
		getattr	= optim_getattr,
		access		= optim_access;
		readlink	= optim_readlink;
		readdir	= optim_readdir;
		mknod		= optim_mknod;
		mkdir		= optim_mkdir;
		symlink	= optim_symlink;
		unlink		= optim_unlink;
		rmdir		= optim_rmdir;
		rename		= optim_rename;
		link		= optim_link;
		chmod		= optim_chmod;
		chown		= optim_chown;
		truncate	= optim_truncate;
		utimens	= optim_utimens;
		open		= optim_open;
		read		= optim_read;
		write		= optim_write;
		statfs		= optim_statfs;
		release	= optim_release;
		fsync		= optim_fsync;
	}

}optim_oper_init;

int main(int argc, char *argv[])
{
	//Read the config file before starting
	int ret;
	initDatabase(argv[argc-2],&db);
	ret = readConfigFile(argv[argc-1]);
	if(ret < 0)
	{
		exit(-1);
	}
	
	//Read files for each folder from sourcePath and store into db
	getFileInfo();
	
	//Initializations done. Start FUSE
	umask(0);
	return fuse_main(argc-2, argv, &optim_oper_init , NULL);
}

//New functions start here

//Reading the config file
int readConfigFile(char *configFilePath)
{
	
	//Init config file
	int i;
	char *value;
	int ret = initConfig(configFilePath);
	if(ret == -1)
	{
		fprintf(stderr,"Configuration Failed. Exiting");
		exit(-1);
	}
	
	arraycontainer *configSections = (arraycontainer *) malloc(sizeof(arraycontainer));
	if(configSections == NULL)
	{
		fprintf(stderr,"Malloc Failed");
		exit(-1);
	}
	
	//Get Sections
	ret = getConfigSections(configSections);
	
	if(ret == -1)
	{
		fprintf(stderr,"Configuration Failed. Exiting");
		exit(-1);
	}	
	
	//Get all key values
	folderInfo.numFolders = configSections->numelements;
	folderInfo.folderData = (folderstruct **)malloc(sizeof(folderstruct) * folderInfo.numFolders);
	if(folderInfo.folderData == NULL)
	{
		exit(-1);
	}
	for (i= 0; i < configSections->numelements; i++)
	{	
		folderInfo.folderData[i] = (folderstruct *)malloc(sizeof(folderstruct));
		
		folderInfo.folderData[i]->rules = (arraycontainer*)malloc(sizeof(arraycontainer));
		folderInfo.folderData[i]->tagList = (arraycontainer*)malloc(sizeof(arraycontainer));
		
		value = getConfigValue(configSections->dataarray[i],"FolderName",value);
		strcpy(folderInfo.folderData[i]->folderName,value);
		value = getConfigValue(configSections->dataarray[i],"SourcePath",value);
		strcpy(folderInfo.folderData[i]->sourcePath,value);
		value = NULL;
		value = getConfigValue(configSections->dataarray[i],"TableName",value);
		strcpy(folderInfo.folderData[i]->tableName,value);
		getConfigRule(configSections->dataarray[i],"Rule",folderInfo.folderData[i]->rules);
		getConfigRule(configSections->dataarray[i],"Tags",folderInfo.folderData[i]->tagList);

//		folderInfo.folderData[i]->rules
		
	}
	
	free(configSections);
	return 0;
}

int getFileInfo()
{

	int i = 0;
	int j = 0;
	for(i=0; i < folderInfo.numFolders; i++)
	{
		//Read the files from sourcePath for each folder
	
		glob_t data;

		if ( glob(folderInfo.folderData[i]->sourcePath, 0, NULL, &data) == 0)
		{
			for(j=0; j < data.gl_pathc; j++)
			{
				printf( "\n Now reading - %s\n", data.gl_pathv[j] );
				readFileData(data.gl_pathv[j]);
			}
		}

		globfree( &data );
	}
}

int FindFolderInfo(char *currPath)
{	
	int i = 0;
	for(i =0; i < folderInfo.numFolders; i++)
	{
		if(!strcmp(folderInfo.folderData[i]->folderName,currPath))
		{
			break;
		}
	}

	if(i == folderInfo.numFolders)
		return -1;
	return i;
}
int readFileData(char *path,int isUpdate = 0)
{
	//Get stat info 
	int i;
	struct stat stbuf;
	int rc  = stat(path,&stbuf);
	int ret = insertFileInfo(&db,&stbuf,path);
	
	//If mp3 file, get the associated tag values
	if(strstr(path,".mp3") != NULL) //Replace
	{
		id3filedata id3file;
		initid3Struct(&id3file);
		getInfo(path,&id3file);
		ret = insertmp3Info(&db,&id3file,isUpdate);
		freeid3Struct(&id3file);
	}
	else if((strstr(path,".jpeg") != NULL) || strstr(path,".jpg") != NULL)
	{
		exiffiledata exifdata;
		initEXIFStruct(&exifdata);
		if (getEXIFInfo(path,&exifdata) != -1)
		{
			ret = insertjpgInfo(&db,&exifdata,isUpdate);
			freeEXIFStruct(&exifdata);
		}
	}
	else
	{
		int numTags = folderInfo.folderData[folderInfo.numFolders - 1]->tagList->numelements;
		keyvaluecontainer container;
		container.keyarray = (char **) malloc (sizeof(char *) * numTags);
		container.valuearray = (char **) malloc (sizeof(char *) * numTags);
		for (i = 0; i < numTags; i++)
		{
			container.keyarray[i] = strdup(folderInfo.folderData[folderInfo.numFolders - 1]->tagList->dataarray[i]);
			container.numelements++;
		}
		if (getNamedAttributes(path,&container) > 0)
		{
			insertCustomInfo(&db,&container,path);
			freeNamedAttributes(&container);
		}
	}
}

int getFilePath(char *path,char **filePath)
{
	//Rename real file, update in db. 
	//remove from file system, remove from db
	//GetLevels
	int res = 0;
	char ** level_key;
	char *folderPath;
	int numLevels = getLevelKeys(&level_key,path);	
	
	//Find the FolderInfo for the corresponding folder
	if(numLevels == 0)
	{
		folderPath = path;
	}
	else
	{
		folderPath = level_key[0];
	}
	int indexFolderInfo = FindFolderInfo(folderPath);
	if(indexFolderInfo == -1)
	{
		return -1;
	}
	
	if((numLevels-1) <= folderInfo.folderData[indexFolderInfo]->rules->numelements)
	{
		//TODO
		return -1;
	}		
	else
	{
		char *query = prepareQuery(path,"path");
		arraycontainer results;
		int ret  = getData(&db,query,&results);
		*filePath = (char *)malloc(strlen(results.dataarray[0]));
		if(*filePath == NULL)
		{
			exit(-1);
		}
		
		strcpy(*filePath,results.dataarray[0]);
	}

	//freeLevelKeys(level_key);
	return res;
}


int FindMusicFolders(const char *path, void *buf,fuse_fill_dir_t filler)
{
	int isLastFolder = 0;
	char *query =  prepareQuery(path,NULL,&isLastFolder);
	int i = 0,j;
	struct stat st;
	//Only root folder
	if(query == NULL)
	{		
		for(i=0; i < folderInfo.numFolders; i++)
		{
			if (i == folderInfo.numFolders - 1)
			{
				for (j = 0; j < folderInfo.folderData[i]->tagList->numelements; j++)
				{
					stat(folderInfo.folderData[i]->tagList->dataarray[j],&st);
					if (filler(buf,folderInfo.folderData[i]->tagList->dataarray[j], &st, 0))
						break;
				}
			}
			else
			{
				stat(folderInfo.folderData[i]->folderName,&st);
				if (filler(buf,folderInfo.folderData[i]->folderName, &st, 0))
					break;
			}
		}

		return 0;
	}
	
	arraycontainer results;
	int ret  = getData(&db,query,&results);

	//loop and pop filler
	for(i=0; i < results.numelements ; i++)
	{
		stat(results.dataarray[i],&st);
		if(isLastFolder)
		{
			char *temp = strrchr(results.dataarray[i],'/');			
			if (filler(buf,strdup(temp+1), &st, 0))
				break;			
		}
		else
		{
			if (filler(buf,results.dataarray[i], &st, 0))
				break;		
		}
	}

}

int getLevelKeys(char ***level_key,const char *path)
{
	int index = 0,i,numLevels = 0;
	int *songs;
	int count = 0;
	int level_year = 0;
	Node_t *currNode;
	char name[1024];
	*level_key = (char **) malloc ( MAX_LEVELS * sizeof(char *));
	
	for (i = 0; i < MAX_LEVELS;i++)
	{
		(*level_key)[i] = (char *) malloc ( MAX_WORD_LENGTH * sizeof(char));
	}

	numLevels = splitLine(path,*level_key,"/");
	return numLevels;
}

void freeLevelKeys(char **level_key)
{
	int i;
      for (i = 0; i < MAX_LEVELS;i++)
        {
		free(level_key[i]);
	}
	free(level_key);
}

void printMp3List(List_t *list)
{
	Node_t *curr;
	int i = 0;
	curr = list->begin;

	printf("\nPrinting list - ");
	/*while (curr != NULL)
	{
		i++;
		fileData = (filedata *) curr->item;
		printf("\n%d: %d|%s",i,fileData->year,fileData->title);
		curr = curr->next;
	}*/

}



int callback(void *NotUsed, int argc, char **argv, char **azColName)
{
	NotUsed = 0;
	int i;
	for (i = 0; i < argc; i++)
	{
		printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
	}
	printf("\n");
	return 0;
}

int fillData(void *fillInfo, int argc, char **argv, char **azColName)
{
	filler_info *f1 = (filler_info *)fillInfo;
		
	int i;
	for (i = 0; i < argc; i++)
	{
			struct stat st;
			if(!strcmp(azColName[i],f1->colVal))
			{	
				stat(argv[i],&st);
				if (f1->filler(f1->buf,argv[i], &st, 0))
					break;
			}

		//printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
	}
	//printf("\n");
	return 0;
}


char * formSearchQuery(int numLevels,char **level_key,int indexFolderInfo,char *columnName,int *isLastFolder = 0)
{
	char *query = (char *)malloc(2048);
	char *query1 = NULL;
	if(query == NULL)
	{
		exit(-1);
	}
	int first = 0;
	
	memset(query,0,2048);	
	
	char name[1024];
	int i = 0;
	int setCol = 0;
	
	//If column name was null but total levels match for final file, query for path
	if((columnName == NULL) && (numLevels > folderInfo.folderData[indexFolderInfo]->rules->numelements))
	{
		sprintf(query,"Select path from %s ",folderInfo.folderData[indexFolderInfo]->tableName);
		*isLastFolder = 1;
		setCol = 1;		
	}
	//Set select query
	if(columnName != NULL)
	{
		sprintf(query,"Select distinct %s from %s ",columnName,folderInfo.folderData[indexFolderInfo]->tableName);
		setCol = 1;
	}
		
	for( i = 1; (i < numLevels) ; i++)
	{
	
		if(first != 0)
		{
			strcat(query," and ");
		}
		else
		{
			strcat(query," where ");
			first = 1;
		}

		if(i > folderInfo.folderData[indexFolderInfo]->rules->numelements)
		{
			sprintf(name,"fileName = \"%s\"",level_key[i]);
		}
		else
		{	
			sprintf(name,"%s = \"%s\"",folderInfo.folderData[indexFolderInfo]->rules->dataarray[i-1],level_key[i]);
		}
		strcat(query,name);
			
	} //for all levels
	
          if(first != 0)
          {
              strcat(query," and ");
	      
          }
          else
          {
              strcat(query," where ");
              first = 1;
          }

	  //extract the path without the last part
	  char *endPath = strrchr(folderInfo.folderData[indexFolderInfo]->sourcePath,'/');
	  char queryPath[256];
	  memcpy(queryPath,folderInfo.folderData[indexFolderInfo]->sourcePath,endPath-folderInfo.folderData[indexFolderInfo]->sourcePath);
	  queryPath[endPath-folderInfo.folderData[indexFolderInfo]->sourcePath] = 0;
	  sprintf(name,"path like \"%%%s%%\" ",queryPath);
	  strcat(query,name); 


	//If column name set by field
	if(setCol == 0)
	{
		query1 = (char *)malloc(2048);
		if(query1 == NULL)
		{
			exit(-1);
		}
		
		sprintf(query1,"Select distinct %s from %s ",folderInfo.folderData[indexFolderInfo]->rules->dataarray[i-1],folderInfo.folderData[indexFolderInfo]->tableName);
		strcat(query1,query);
		query = query1;
	}
		
	return query;
}

char * prepareQuery(const char *path,char *columnName, int *isLastFolder = 0)
{
	
	char **level_key;
	int numLevels = getLevelKeys(&level_key,path);
	if(numLevels == 0)
	{
		return NULL;
	}

	//Find the FolderInfo for the corresponding folder
	char *folderPath;
	folderPath = level_key[0];
	int indexFolderInfo = FindFolderInfo(folderPath);
	if(indexFolderInfo == -1)
	//{
	//	return -1;
	//}
	//else if (indexFolderInfo == folderInfo.numFolders - 1)
	{
		if (numLevels == 1)
		{
			char *query = (char *) malloc(sizeof(char) * BUFFER_SIZE);
			sprintf(query,"select distinct customValue from customTagInfo WHERE customKey = \"%s\"",level_key[0]);
			printf("\nTrying %s",query);
			return query;
		}
		else
		{

			char *query = (char *) malloc(sizeof(char) * BUFFER_SIZE);
			sprintf(query,"select distinct path from customTagInfo WHERE customKey = \"%s\" AND customValue = \"%s\"",level_key[0],level_key[1]);
			printf("\nlevel2:Trying %s",query);
			return query;
		}

	}
	//if(numLevels > MAX_LEVEL)
		//return 0;
		
	char *query = formSearchQuery(numLevels,level_key,indexFolderInfo,columnName,isLastFolder);
	freeLevelKeys(level_key);
	return query;
}
